package org.xtremeobjects.domain.states.annotated;

import java.lang.reflect.Method;
import org.xtremeobjects.anotations.state.OnStateEntry;
import org.xtremeobjects.anotations.state.OnStateExit;
import org.xtremeobjects.domain.states.State;
import org.xtremeobjects.domain.states.StateMachine;
import org.xtremeobjects.domain.states.StateMachineFactory;
import org.xtremeobjects.domain.states.Transition;

public class StateMachineBuilderFromAnnotatedClass {

	public StateMachineBuilderFromAnnotatedClass( ) {
	}
	
	public StateMachine buildStateMachineForClass(Class<?> clazz) {
		
		StateMachineAnnotatedClass annotatedClass = new StateMachineAnnotatedClass( clazz );
		StateMachine sm = StateMachineFactory.Instance.create( annotatedClass.getStateMachineName( ) );

		addStates(annotatedClass, sm);
		addTransitions(annotatedClass, sm);
		

		for (Method method : annotatedClass.getActions()) {
			OnStateEntry onEntry = method.getAnnotation(OnStateEntry.class);
			OnStateExit onExit = method.getAnnotation(OnStateExit.class);
			if(onEntry!=null)
				addActionForEntry(sm, method, onEntry.value());
			if(onExit!=null)
				addActionForExit(sm, method, onEntry.value());
		}
		
		return sm;
	}

	private void addActionForEntry(StateMachine sm, Method method, String stateName) {
		State state = sm.findStateByName(stateName);
		if(state==null)
			throw new RuntimeException("Invalid state name " + stateName);
		state.addActionForEntry("self." + method.getName() + "()");
	}

	private void addActionForExit(StateMachine sm, Method method, String stateName) {
		State state = sm.findStateByName(stateName);
		if(state==null)
			throw new RuntimeException("Invalid state name " + stateName);
		state.addActionForExit("self." + method.getName() + "()");
	}

	private void addTransitions(StateMachineAnnotatedClass annotatedClass, StateMachine sm) {
		for (Method method : annotatedClass.getEvents()) {
			org.xtremeobjects.anotations.state.Transition transition = method.getAnnotation(org.xtremeobjects.anotations.state.Transition.class);
			if(transition!=null)
				addTransition(sm, method, transition);
			org.xtremeobjects.anotations.state.Event event = method.getAnnotation(org.xtremeobjects.anotations.state.Event.class);
			if(event!=null)
				for (org.xtremeobjects.anotations.state.Transition tr : event.value()) {
					addTransition(sm, method, tr);
				}
		}

		// Add transition to start state
		String startStateName = annotatedClass.getStartState();
		State startState = sm.findStateByName(startStateName);
		if(startState==null)
			throw new RuntimeException("Invalid start state in @StateMachine annotation, state is " + startStateName );
		startState.from(sm.start());
	
	}

	private void addTransition(StateMachine sm, Method method, org.xtremeobjects.anotations.state.Transition transition) {
		State toState = sm.findStateByName(transition.to());
		if(toState==null)
			throw new RuntimeException("Invalid state transition to state " + transition.to());
		for (String fromName : transition.from()) {
			State fromState = sm.findStateByName(fromName);
			if(fromState==null)
				throw new RuntimeException("Invalid state transition from state " + fromName);
			Transition tr = fromState.to(toState);
			if(!transition.guard().isEmpty())
				tr.guard(transition.guard(), transition.guardDescr());
			tr.event(method.getName());
		}
	}

	private void addStates(StateMachineAnnotatedClass annotatedClass, StateMachine sm) {
		for (String  stateName : annotatedClass.getStates()) {
			createNewState(annotatedClass, sm, stateName);
		}
	}

	private State createNewState(StateMachineAnnotatedClass annotatedClass, StateMachine sm, String stateName) {
		State state;
		String superState = annotatedClass.getStateSuperState(stateName);
		if(superState==null)
			state = sm.state( stateName );
		else {
			state = createNewState(annotatedClass, sm, superState);
			state = state.state( stateName );
		}
		return state;
	}
}
